package com.jzo2o.market.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.ActivityStatusEnum;
import com.jzo2o.market.mapper.ActivityMapper;
import com.jzo2o.market.model.domain.Activity;
import com.jzo2o.market.model.dto.request.ActivityQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.ActivitySaveReqDTO;
import com.jzo2o.market.model.dto.response.ActivityInfoResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.IActivityService;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.ACTIVITY_CACHE_LIST;
import static com.jzo2o.market.constants.RedisConstants.RedisKey.COUPON_RESOURCE_STOCK;
import static com.jzo2o.market.enums.ActivityStatusEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 */
@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate<Object, Object> redisTemplate;
    @Resource
    private ICouponService couponService;
    @Resource
    private ICouponWriteOffService couponWriteOffService;
    @Resource
    private ActivityServiceImpl owner;


    /**
     * 保存优惠券活动接口
     * 作用：新增或修改一个活动信息
     */
    @Override
    public void saveActivity(ActivitySaveReqDTO activitySaveReqDTO) {
        // 校验参数
        activitySaveReqDTO.check();
        // 1、设置活动信息
        Activity activity = BeanUtils.toBean(activitySaveReqDTO, Activity.class);
        activity.setStatus(NO_DISTRIBUTE.getStatus()); // 状态
        // 库存
        activity.setStockNum(activitySaveReqDTO.getTotalNum());
        // 2、判断是否为新增活动并生成 ID - 只有修改时需要id。所以id为null则是新增
        if(activitySaveReqDTO.getId() == null) {
            activity.setId(IdUtils.getSnowflakeNextId()); // 生成 ID
        }
        //排序字段
        // long sortBy = DateUtils.toEpochMilli(activity.getDistributeStartTime()) * MILLION + activity.getId() % MILLION;
        // 3、添加/修改活动至数据库
        owner.saveOrUpdate(activity);
    }

    /**
     * 分页查询优惠券活动
     */
    @SuppressWarnings("unchecked") // 抑制未检查的泛型数组创建警告
    @Override
    public PageResult<ActivityInfoResDTO> queryForPage(ActivityQueryForPageReqDTO activityQueryForPageReqDTO) {
        // 获取当前时间（后续可能用于时间相关的查询条件）
        LocalDateTime now = DateUtils.now();
        // 1. 初始化查询条件构造器
        LambdaQueryWrapper<Activity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 构建动态查询条件（参数不为空时才添加对应条件）
        lambdaQueryWrapper.eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getId()), Activity::getId, activityQueryForPageReqDTO.getId()) // id
                .like(StringUtils.isNotEmpty(activityQueryForPageReqDTO.getName()), Activity::getName, activityQueryForPageReqDTO.getName()) // 活动名称
                .eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getType()), Activity::getType, activityQueryForPageReqDTO.getType()) // 活动类型
                .eq(ObjectUtils.isNotNull(activityQueryForPageReqDTO.getStatus()), Activity::getStatus, activityQueryForPageReqDTO.getStatus()); // 活动状态
        // 2. 设置排序规则：按活动ID倒序（最新创建的在前）
        lambdaQueryWrapper.orderByDesc(Activity::getId);
        // 3. 初始化分页参数（页码、每页条数）
        Page<Activity> activityPage = new Page<>(activityQueryForPageReqDTO.getPageNo().intValue(), activityQueryForPageReqDTO.getPageSize().intValue());
        // 执行分页查询
        activityPage = baseMapper.selectPage(activityPage, lambdaQueryWrapper);
        // 将查询结果（Activity实体）转换为响应DTO（ActivityInfoResDTO）并返回分页结果
        return PageUtils.toPage(activityPage, ActivityInfoResDTO.class);
    }

    /**
     * 查询优惠券活动详情
     */
    @Override
    public ActivityInfoResDTO queryById(Long id) {
        // 1.获取活动
        Activity activity = baseMapper.selectById(id);
        // 判空
        if (activity == null) {
            return new ActivityInfoResDTO();
        }
        // 2.数据转换，并返回信息
        ActivityInfoResDTO activityInfoResDTO = BeanUtils.toBean(activity, ActivityInfoResDTO.class);
        // 设置状态
        // activityInfoResDTO.setStatus(getStatus(activity.getDistributeStartTime(), activity.getDistributeEndTime(), activity.getStatus()));
        // 3.(已)领取数量
        // Integer receiveNum = couponService.countReceiveNumByActivityId(activity.getId());
        Integer receiveNum = activity.getTotalNum()-activity.getStockNum(); // 发放数量-库存数量=(已)领取数量
        activityInfoResDTO.setReceiveNum(receiveNum);
        // 4.核销量
        Integer writeOffNum = couponWriteOffService.countByActivityId(id);
        activityInfoResDTO.setWriteOffNum(NumberUtils.null2Zero(writeOffNum)); //若为空则设置为0
        // 5.返回
        return activityInfoResDTO;
    }


    /**
     * 活动撤销
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long id) {
        // 1.活动作废
        boolean update = lambdaUpdate()
                .set(Activity::getStatus, ActivityStatusEnum.VOIDED.getStatus()) // 状态改换为作废
                .eq(Activity::getId, id) // 指定活动ID
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus())) // 状态为待生效或进行中
                .update();
        if(!update) {
            return;
        }
        // 2.未使用优惠券作废
        couponService.revoke(id);
    }


    /**
     * 更新活动状态
     */
    @Override
    public void updateStatus() {
        LocalDateTime now = DateUtils.now();
        // 1.更新已经进行中的状态
        lambdaUpdate()
                .set(Activity::getStatus, ActivityStatusEnum.DISTRIBUTING.getStatus())//更新活动状态为进行中
                .eq(Activity::getStatus, NO_DISTRIBUTE)//检索待生效的活动
                .le(Activity::getDistributeStartTime, now)//活动开始时间小于等于当前时间
                .gt(Activity::getDistributeEndTime,now)//活动结束时间大于当前时间
                .update();
        // 2.更新已经结束的
        lambdaUpdate()
                .set(Activity::getStatus, LOSE_EFFICACY.getStatus())//更新活动状态为已失效
                .in(Activity::getStatus, Arrays.asList(DISTRIBUTING.getStatus(), NO_DISTRIBUTE.getStatus()))//检索待生效及进行中的活动
                .lt(Activity::getDistributeEndTime, now)//活动结束时间小于当前时间
                .update();
    }


    /**
     * 抢卷预热
     */
    // 忽略警告
    @SuppressWarnings("unchecked")
    @Override
    public void preHeat() {
        /**
         *SELECT *FROM activity tWHERE t.distribute_start_time <= DATE_ADD(NOW(), INTERVAL 30 DAY) AND t.status IN (1, 2)
         *ORDER BY t.distribute_start_time ASC;
         */
        // 1.查询准备
        LocalDateTime now = DateUtils.now();
        LocalDateTime preHeatTime = now.plusDays(30);
        LambdaQueryWrapper<Activity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper
                .le(Activity::getDistributeStartTime, preHeatTime) // 活动开始时间小于等于当前时间 + 30天
                .in(Activity::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus())) // 状态为待生效或进行中
                .orderByAsc(Activity::getDistributeStartTime);
        // 查询
        List<Activity> activities = baseMapper.selectList(lambdaQueryWrapper);
        if (CollUtils.isEmpty(activities)) {
            //防止缓存穿透
            activities = new ArrayList<>();
        }
        // 2.数据转换： 将List<Activity> 转为List<SeizeCouponInfoResDTO>
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = BeanUtils.copyToList(activities, SeizeCouponInfoResDTO.class);
        // 3.再转为json字符串
        String json = JsonUtils.toJsonStr(seizeCouponInfoResDTOS);
        // 4.存入redis，操作string用opsForValue()，操作哈希用opsForHash()
        redisTemplate.opsForValue().set(ACTIVITY_CACHE_LIST, json);

        // 5.对未开始的活动的库存直接更新到redis
        activities.stream().filter(v->getStatus(v.getDistributeStartTime(),v.getDistributeEndTime(),v.getStatus())==NO_DISTRIBUTE.getStatus()).forEach(v->{
            redisTemplate.opsForHash().put(String.format(COUPON_RESOURCE_STOCK, v.getId() % 10), v.getId(), v.getTotalNum());
        });

        // 6.对已经开始的活动的库存，如果redis中没有，则更新到redis
        activities.stream().filter(v->getStatus(v.getDistributeStartTime(),v.getDistributeEndTime(),v.getStatus())==DISTRIBUTING.getStatus()).forEach(v->{
            redisTemplate.opsForHash().putIfAbsent(String.format(COUPON_RESOURCE_STOCK, v.getId() % 10), v.getId(), v.getTotalNum());
        });
    }


    /**
     * 查询抢卷信息
     * @param tabType 页面类型
     * @return 抢卷信息
     */
    @Override
    public List<SeizeCouponInfoResDTO> queryForListWithCache(Integer tabType) {
        //从redis查询活动信息
        Object seizeCouponInfoStr = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (ObjectUtils.isNull(seizeCouponInfoStr)) {
            return CollUtils.emptyList();
        }
        //将json转为List
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = JsonUtils.toList(seizeCouponInfoStr.toString(), SeizeCouponInfoResDTO.class);
        //根据tabType确定要查询的状态
        int queryStatus = tabType == TabTypeConstants.SEIZING ? DISTRIBUTING.getStatus() : NO_DISTRIBUTE.getStatus();
        //过滤数据，并设置剩余数量、实际状态
        List<SeizeCouponInfoResDTO> collect = seizeCouponInfoResDTOS.stream()
                .filter(item -> queryStatus == getStatus(item.getDistributeStartTime(), item.getDistributeEndTime(), item.getStatus()))
                .peek(item -> {
                    //剩余数量
                    item.setRemainNum(item.getStockNum());
                    //状态
                    item.setStatus(queryStatus);
                }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 获取状态，
     * 用于xxl或其他定时任务在高性能要求下无法做到实时状态
     * @return 状态
     */
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        if (NO_DISTRIBUTE.equals(status) &&
                distributeStartTime.isBefore(DateUtils.now()) &&
                distributeEndTime.isAfter(DateUtils.now())) {//待生效状态，实际活动已开始
            return DISTRIBUTING.getStatus();
        }else if(NO_DISTRIBUTE.equals(status) &&
                distributeEndTime.isBefore(DateUtils.now())){//待生效状态，实际活动已结束
            return LOSE_EFFICACY.getStatus();
        }else if (DISTRIBUTING.equals(status) &&
                distributeEndTime.isBefore(DateUtils.now())) {//进行中状态，实际活动已结束
            return LOSE_EFFICACY.getStatus();
        }
        return status;
    }


    /**
     * 获取活动信息
     * @param id 活动id
     * @return 活动信息
     */
    @Override
    public ActivityInfoResDTO getActivityInfoByIdFromCache(Long id) {
        // 1.从缓存中获取活动信息
        Object activityList = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (ObjectUtils.isNull(activityList)) {
            return null;
        }
        // 2.过滤指定活动信息
        List<ActivityInfoResDTO> list = JsonUtils.toList(activityList.toString(), ActivityInfoResDTO.class);
        if (CollUtils.isEmpty(list)) {
            return null;
        }
        // 3.过滤指定活动
        return list.stream()
                .filter(activityInfoResDTO -> activityInfoResDTO.getId().equals(id))
                .findFirst().orElse(null);
    }


    /**
     * 扣减库存
     * @param id 活动id
     *  如果扣减库存失败抛出异常
     */
    public void deductStock(Long id){
        boolean update = lambdaUpdate()
                .setSql("stock_num = stock_num-1")
                .eq(Activity::getId, id)
                .gt(Activity::getStockNum, 0)
                .update();
        if(!update){
            throw new CommonException("扣减优惠券库存失败，活动id:"+id);
        }
    }


}
